#include "QtStyle.hpp"

#include <QtCore/QFile>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonValueRef>
#include <QtCore/QJsonArray>

#include <QRandomGenerator>

#include <QDebug>

inline void initResources() { Q_INIT_RESOURCE(resources); }

namespace Qt
{
	NodeStyle::
		NodeStyle()
	{
		// Explicit resources inialization for preventing the static initialization
		// order fiasco: https://isocpp.org/wiki/faq/ctors#static-init-order
		initResources();

		// This configuration is stored inside the compiled unit and is loaded statically
		loadJsonFile(":DefaultStyle.json");
	}


	NodeStyle::
		NodeStyle(QString jsonText)
	{
		loadJsonText(jsonText);
	}


	void
		NodeStyle::
		setNodeStyle(QString jsonText)
	{
		NodeStyle style(jsonText);


		StyleCollection::setNodeStyle(style);
	}


#ifdef STYLE_DEBUG
#define NODE_STYLE_CHECK_UNDEFINED_VALUE(v, variable) { \
      if (v.type() == QJsonValue::Undefined || \
          v.type() == QJsonValue::Null) \
        qWarning() << "Undefined value for parameter:" << #variable; \
  }
#else
#define NODE_STYLE_CHECK_UNDEFINED_VALUE(v, variable)
#endif

#define NODE_STYLE_READ_COLOR(values, variable)  { \
    auto valueRef = values[#variable]; \
    NODE_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \
    if (valueRef.isArray()) { \
      auto colorArray = valueRef.toArray(); \
      std::vector<int> rgb; rgb.reserve(3); \
      for (auto it = colorArray.begin(); it != colorArray.end(); ++it) { \
        rgb.push_back((*it).toInt()); \
      } \
      variable = QColor(rgb[0], rgb[1], rgb[2]); \
    } else { \
      variable = QColor(valueRef.toString()); \
    } \
}

#define NODE_STYLE_READ_FLOAT(values, variable)  { \
    auto valueRef = values[#variable]; \
    NODE_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \
    variable = valueRef.toDouble(); \
}

	void
		NodeStyle::
		loadJsonFile(QString styleFile)
	{
		QFile file(styleFile);

		if (!file.open(QIODevice::ReadOnly))
		{
			qWarning() << "Couldn't open file " << styleFile;

			return;
		}

		loadJsonFromByteArray(file.readAll());
	}


	void
		NodeStyle::
		loadJsonText(QString jsonText)
	{
		loadJsonFromByteArray(jsonText.toUtf8());
	}


	void
		NodeStyle::
		loadJsonFromByteArray(QByteArray const &byteArray)
	{
		QJsonDocument json(QJsonDocument::fromJson(byteArray));

		QJsonObject topLevelObject = json.object();

		QJsonValueRef nodeStyleValues = topLevelObject["NodeStyle"];

		QJsonObject obj = nodeStyleValues.toObject();

		NODE_STYLE_READ_COLOR(obj, NormalBoundaryColor);
		NODE_STYLE_READ_COLOR(obj, SelectedBoundaryColor);
		NODE_STYLE_READ_COLOR(obj, GradientColor0);
		NODE_STYLE_READ_COLOR(obj, GradientColor1);
		NODE_STYLE_READ_COLOR(obj, GradientColor2);
		NODE_STYLE_READ_COLOR(obj, GradientColor3);
		NODE_STYLE_READ_COLOR(obj, ShadowColor);
		NODE_STYLE_READ_COLOR(obj, FontColor);
		NODE_STYLE_READ_COLOR(obj, FontColorFaded);
		NODE_STYLE_READ_COLOR(obj, ConnectionPointColor);
		NODE_STYLE_READ_COLOR(obj, FilledConnectionPointColor);
		NODE_STYLE_READ_COLOR(obj, WarningColor);
		NODE_STYLE_READ_COLOR(obj, ErrorColor);

		NODE_STYLE_READ_COLOR(obj, HotKeyColor0);
		NODE_STYLE_READ_COLOR(obj, HotKeyColor1);
		NODE_STYLE_READ_COLOR(obj, HotKeyColor2);

		NODE_STYLE_READ_FLOAT(obj, PenWidth);
		NODE_STYLE_READ_FLOAT(obj, HoveredPenWidth);
		NODE_STYLE_READ_FLOAT(obj, ConnectionPointDiameter);

		NODE_STYLE_READ_FLOAT(obj, Opacity);
	}

	ConnectionStyle::
		ConnectionStyle()
	{
		// Explicit resources inialization for preventing the static initialization
		// order fiasco: https://isocpp.org/wiki/faq/ctors#static-init-order
		initResources();

		// This configuration is stored inside the compiled unit and is loaded statically
		loadJsonFile(":DefaultStyle.json");
	}


	ConnectionStyle::
		ConnectionStyle(QString jsonText)
	{
		loadJsonFile(":DefaultStyle.json");
		loadJsonText(jsonText);
	}


	void
		ConnectionStyle::
		setConnectionStyle(QString jsonText)
	{
		ConnectionStyle style(jsonText);

		StyleCollection::setConnectionStyle(style);
	}

#ifdef STYLE_DEBUG
#define CONNECTION_STYLE_CHECK_UNDEFINED_VALUE(v, variable) { \
      if (v.type() == QJsonValue::Undefined || \
          v.type() == QJsonValue::Null) \
        qWarning() << "Undefined value for parameter:" << #variable; \
  }
#else
#define CONNECTION_STYLE_CHECK_UNDEFINED_VALUE(v, variable)
#endif


#define CONNECTION_VALUE_EXISTS(v) \
  (v.type() != QJsonValue::Undefined && \
   v.type() != QJsonValue::Null)

#define CONNECTION_STYLE_READ_COLOR(values, variable)  { \
    auto valueRef = values[#variable]; \
    CONNECTION_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \
    if (CONNECTION_VALUE_EXISTS(valueRef)) {\
      if (valueRef.isArray()) { \
        auto colorArray = valueRef.toArray(); \
        std::vector<int> rgb; rgb.reserve(3); \
        for (auto it = colorArray.begin(); it != colorArray.end(); ++it) { \
          rgb.push_back((*it).toInt()); \
        } \
        variable = QColor(rgb[0], rgb[1], rgb[2]); \
      } else { \
        variable = QColor(valueRef.toString()); \
      } \
    } \
}

#define CONNECTION_STYLE_READ_FLOAT(values, variable)  { \
    auto valueRef = values[#variable]; \
    CONNECTION_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \
    if (CONNECTION_VALUE_EXISTS(valueRef)) \
      variable = valueRef.toDouble(); \
}

#define CONNECTION_STYLE_READ_BOOL(values, variable)  { \
    auto valueRef = values[#variable]; \
    CONNECTION_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \
    if (CONNECTION_VALUE_EXISTS(valueRef)) \
      variable = valueRef.toBool(); \
}

	void
		ConnectionStyle::
		loadJsonFile(QString styleFile)
	{
		QFile file(styleFile);

		if (!file.open(QIODevice::ReadOnly))
		{
			qWarning() << "Couldn't open file " << styleFile;

			return;
		}

		loadJsonFromByteArray(file.readAll());
	}


	void
		ConnectionStyle::
		loadJsonText(QString jsonText)
	{
		loadJsonFromByteArray(jsonText.toUtf8());
	}


	void
		ConnectionStyle::
		loadJsonFromByteArray(QByteArray const &byteArray)
	{
		QJsonDocument json(QJsonDocument::fromJson(byteArray));

		QJsonObject topLevelObject = json.object();

		QJsonValueRef nodeStyleValues = topLevelObject["ConnectionStyle"];

		QJsonObject obj = nodeStyleValues.toObject();

		CONNECTION_STYLE_READ_COLOR(obj, ConstructionColor);
		CONNECTION_STYLE_READ_COLOR(obj, NormalColor);
		CONNECTION_STYLE_READ_COLOR(obj, SelectedColor);
		CONNECTION_STYLE_READ_COLOR(obj, SelectedHaloColor);
		CONNECTION_STYLE_READ_COLOR(obj, HoveredColor);

		CONNECTION_STYLE_READ_FLOAT(obj, LineWidth);
		CONNECTION_STYLE_READ_FLOAT(obj, ConstructionLineWidth);
		CONNECTION_STYLE_READ_FLOAT(obj, PointDiameter);

		CONNECTION_STYLE_READ_BOOL(obj, UseDataDefinedColors);
	}


	QColor
		ConnectionStyle::
		constructionColor() const
	{
		return ConstructionColor;
	}


	QColor
		ConnectionStyle::
		normalColor() const
	{
		return NormalColor;
	}


	QColor
		ConnectionStyle::
		normalColor(QString typeId) const
	{
		std::size_t hash = qHash(typeId);

		std::size_t const hue_range = 0xFF;

		QRandomGenerator gen(hash);
		std::size_t hue = gen.bounded((int)0, (int)hue_range);//qrand() % hue_range;

		std::size_t sat = 120 + hash % 129;

		return QColor::fromHsl(hue,
			sat,
			160);
	}


	QColor
		ConnectionStyle::
		selectedColor() const
	{
		return SelectedColor;
	}


	QColor
		ConnectionStyle::
		selectedHaloColor() const
	{
		return SelectedHaloColor;
	}


	QColor
		ConnectionStyle::
		hoveredColor() const
	{
		return HoveredColor;
	}


	float
		ConnectionStyle::
		lineWidth() const
	{
		return LineWidth;
	}


	float
		ConnectionStyle::
		constructionLineWidth() const
	{
		return ConstructionLineWidth;
	}


	float
		ConnectionStyle::
		pointDiameter() const
	{
		return PointDiameter;
	}


	bool
		ConnectionStyle::
		useDataDefinedColors() const
	{
		return UseDataDefinedColors;
	}

	FlowViewStyle::
		FlowViewStyle()
	{
		// Explicit resources inialization for preventing the static initialization
		// order fiasco: https://isocpp.org/wiki/faq/ctors#static-init-order
		initResources();

		// This configuration is stored inside the compiled unit and is loaded statically
		loadJsonFile(":DefaultStyle.json");
	}


	FlowViewStyle::
		FlowViewStyle(QString jsonText)
	{
		loadJsonText(jsonText);
	}


	void
		FlowViewStyle::
		setStyle(QString jsonText)
	{
		FlowViewStyle style(jsonText);

		StyleCollection::setFlowViewStyle(style);
	}


#ifdef STYLE_DEBUG
#define FLOW_VIEW_STYLE_CHECK_UNDEFINED_VALUE(v, variable) { \
      if (v.type() == QJsonValue::Undefined || \
          v.type() == QJsonValue::Null) \
        qWarning() << "Undefined value for parameter:" << #variable; \
  }
#else
#define FLOW_VIEW_STYLE_CHECK_UNDEFINED_VALUE(v, variable)
#endif

#define FLOW_VIEW_STYLE_READ_COLOR(values, variable)  { \
    auto valueRef = values[#variable]; \
    FLOW_VIEW_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \
    if (valueRef.isArray()) { \
      auto colorArray = valueRef.toArray(); \
      std::vector<int> rgb; rgb.reserve(3); \
      for (auto it = colorArray.begin(); it != colorArray.end(); ++it) { \
        rgb.push_back((*it).toInt()); \
      } \
      variable = QColor(rgb[0], rgb[1], rgb[2]); \
    } else { \
      variable = QColor(valueRef.toString()); \
    } \
}

	void
		FlowViewStyle::
		loadJsonFile(QString styleFile)
	{
		QFile file(styleFile);

		if (!file.open(QIODevice::ReadOnly))
		{
			qWarning() << "Couldn't open file " << styleFile;

			return;
		}

		loadJsonFromByteArray(file.readAll());
	}


	void
		FlowViewStyle::
		loadJsonText(QString jsonText)
	{
		loadJsonFromByteArray(jsonText.toUtf8());
	}


	void
		FlowViewStyle::
		loadJsonFromByteArray(QByteArray const &byteArray)
	{
		QJsonDocument json(QJsonDocument::fromJson(byteArray));

		QJsonObject topLevelObject = json.object();

		QJsonValueRef nodeStyleValues = topLevelObject["FlowViewStyle"];

		QJsonObject obj = nodeStyleValues.toObject();

		FLOW_VIEW_STYLE_READ_COLOR(obj, BackgroundColor);
		FLOW_VIEW_STYLE_READ_COLOR(obj, FineGridColor);
		FLOW_VIEW_STYLE_READ_COLOR(obj, CoarseGridColor);
	}

	NodeStyle const&
		StyleCollection::
		nodeStyle()
	{
		return instance()._nodeStyle;
	}


	ConnectionStyle const&
		StyleCollection::
		connectionStyle()
	{
		return instance()._connectionStyle;
	}


	FlowViewStyle const&
		StyleCollection::
		flowViewStyle()
	{
		return instance()._flowViewStyle;
	}


	void
		StyleCollection::
		setNodeStyle(NodeStyle nodeStyle)
	{
		instance()._nodeStyle = nodeStyle;
	}


	void
		StyleCollection::
		setConnectionStyle(ConnectionStyle connectionStyle)
	{
		instance()._connectionStyle = connectionStyle;
	}


	void
		StyleCollection::
		setFlowViewStyle(FlowViewStyle flowViewStyle)
	{
		instance()._flowViewStyle = flowViewStyle;
	}

	StyleCollection&
		StyleCollection::
		instance()
	{
		static StyleCollection collection;

		return collection;
	}
}
